﻿/*
  The contents of this file are subject to the Mozilla Public License Version
  1.1 (the "License"); you may not use this file except in compliance with
  the License. You may obtain a copy of the License at 
  http://www.mozilla.org/MPL/ 
  
  Software distributed under the License is distributed on an "AS IS" basis,
  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  for the specific language governing rights and limitations under the License. 
  
  The Original Code is [eden: ECMAScript data exchange notation AS3]. 
  
  The Initial Developer of the Original Code is
  Zwetan Kjukov <zwetan@gmail.com>.
  Portions created by the Initial Developer are Copyright (C) 2004-2011
  the Initial Developer. All Rights Reserved.
  
  Contributor(s):
  Marc Alcaraz <ekameleon@gmail.com>.
  
*/

package library.eden
{
    import core.chars.isAlpha;
    import core.chars.isDigit;
    import core.chars.isHexDigit;
    import core.chars.isLineTerminator;
    import core.log;
    import core.output;
    import core.reflect.*;
    import core.strings.endsWith;
    import core.strings.format;
    import core.strings.startsWith;

    // FIXME // LOG::P{  } ?
    
    /**
     * The ECMAScript parser class.
     */
    public class ECMAScript
    {
        /* note:
           a pool contains string indexes to object references
           (yeah this could be surely optimized with a Dictionnary)
           ex:
           (code)
           _globalPool[ "system.serializers.eden.config" ] = system.serializers.eden.config;
           _localPool[ "x.y.z" ] = localScope.x.y.z;
           (end)
         */
        private static var _globalPool:Array = [];
        
        private var _localPool:Array = [];
        
        private var _source:String;
        private var _logs:Array;
        private var _1char:Boolean;
        
        /**
         * Object Replacement Character
         * @private
         */
        private var _ORC:String = "\uFFFC";
        
        private var _singleValue:Boolean = true;
        private var _inAssignement:Boolean;
        //private var _inFunction:Boolean;
        private var _inConstructor:int;
        
        /** The current character to parse. */
        public var ch:String;
        
        /** The current parser position in the string expression to parse */
        public var pos:uint;
        
        /** The local scope reference. */
        public var localscope:*;
        
        /** The comments string representation. */
        public var comments:String;
        
        /**
         * Specifies whether errors encountered by the renderer are reported to the application.
         * 
         * When enableErrorChecking is <code>true</code> methods are synchronous and can throw errors.
         * 
         * When enableErrorChecking is <code>false</code>, the default, the methods are asynchronous and errors are not reported.
         * 
         * Enabling error checking reduces parsing performance.
         * You should only enable error checking when debugging.
         */
        public var enableErrorChecking:Boolean;
        
        /**
         * Creates a new ECMAScript instance.
         * @param source The string expression to parse.
         */
        public function ECMAScript( source:String, enableErrorChecking:Boolean = false )
        {
            LOG::P{ log.i( "ECMAScript ctor" ); }
            this.enableErrorChecking = enableErrorChecking;
            
            _source = source;
            _logs   = [];
            
            pos        = 0;
            ch         = "";
            localscope = {};
            comments   = "";
        }
        
        /* group: static methods */
        
        /** Evaluate the specified string source value with the parser. */
        public static function evaluate( source:String, enableErrorChecking:Boolean = false ):*
        {
            LOG::P{ log.i( "static evaluate()" ); }
            var parser:ECMAScript = new ECMAScript( source, enableErrorChecking );
            return parser.eval();
        }
        
        
        /* group: static utils */
        
        /** Indicates if the specified path is authorized in the config.authorized Array. */
        public static function isAuthorized( path:String ):Boolean
        {
            LOG::P{ log.i( "static isAuthorized( \"" +path+ "\" )" ); }
            var authorized:Array = metadata.config.authorized;
            
            var replace:Function = function( str:String , search:String , replace:String ):String 
            {
                return str.split( search ).join( replace );
            };
            
            if ( authorized == null || authorized.length == 0 ) { return false; }
            
            var pathMode:Boolean;
            var firstLetter:String = path.charAt( 0 );
            
            if( path.indexOf( "." ) > -1 ) { pathMode = true; }
            
            if( !metadata.config.strictMode ) { firstLetter = firstLetter.toLowerCase(); }
            
            var filterFirstLetter:Function = function( value:*, index:int, arrObj:Array ):*
            {
                if( !metadata.config.strictMode ) { value = value.toLowerCase(); }
                return startsWith( value, firstLetter ) ;
            };
            
            var whiteList:Array = authorized.filter( filterFirstLetter );
            if( whiteList && whiteList.length == 0 ) { return false; }
            
            var cur:String;
            var len:int = whiteList.length;
            for( var i:int ; i<len ; i++ )
            {
                cur = whiteList[i];
                
                if( cur == path ) { return true; }
                
                if( pathMode )
                {
                    if ( endsWith( cur, ".*" ) )
                    {
                        if ( startsWith( path, replace( cur, ".*", "" ) ) ) { return true; }
                    }
                }
                else if( path == replace( cur, ".*", "" ) ) { return true; }
            }
            
            return false;
        }
        
        /** Indicates if the specified expression is a digit number value. */
        public static function isDigitNumber( num:String ):Boolean
        {
            LOG::P{ log.i( "static isDigitNumber( \"" + num + "\" )" ); }
            var numarr:Array = num.split( "" );
            for( var i:int = 0; i < numarr.length ; i++ )
            {
                if( !isDigit( numarr[i] ) ) { return false; }
            }
            
            return true;
        }
        
        /**
         * Indicates if the specified expression is a start identifier.
         * @see ECMA-262 spec 7.6 (PDF p26/188)
         */
        public static function isIdentifierStart( c:String ):Boolean
        {
            LOG::P{ log.i( "static isIdentifierStart( \"" + c + "\" )" ); }
            
            return ( isAlpha( c ) || (c == "_") || (c == "$" ) ) ;
            
            //if( c.charCodeAt( 0 ) < 128 ) { return false; }
            //return false;
        }
        
        /**
         * Indicates if the identifier is a part.
         */
        public static function isIdentifierPart( c:String ):Boolean
        {
            LOG::P{ log.i( "static isIdentifierPart( \"" + c + "\" )" ); }
            
            return isIdentifierStart( c ) || isDigit( c ) ;
            
            //if( isIdentifierStart( c ) ) { return true; }
            //if( isDigit( c ) ) { return true; }
            //if( c.charCodeAt( 0 ) < 128 ) { return false; }
            //return false;
        }
        
        /**
         * Indicates if the specified identifier string value is a future reserved keyword.  
         * <p><b>Note :</b> Future Reserved Keywords in ECMA-262 spec 7.5.3</p>
         */
        public static function isFutureReservedKeyword( identifier:String ):Boolean
        {
            LOG::P{ log.i( "static isFutureReservedKeyword( \"" + identifier + "\" )" ); }
            if( !metadata.config.strictMode ) { identifier = identifier.toLowerCase(); }
            if( metadata.config.reservedFuture.indexOf( identifier ) > -1 ) { return true; }
            
            return false;
        }
        
        /**
         * Indicates if the specified indentifier is a reserved keyword.
         * Reserved Keywords see : <b>ECMA-262 spec 7.5.2 p13 (PDF p25/188)</b>
         */
        public static function isReservedKeyword( identifier:String ):Boolean
        {
            LOG::P{ log.i( "static isReservedKeyword( \"" + identifier + "\" )" ); }
            if( !metadata.config.strictMode ) { identifier = identifier.toLowerCase(); }
            if( metadata.config.reserved.indexOf( identifier ) > -1 ) { return true; }
            
            return false;
        }
        
        /* group: private utils */
        
        /**
         * Dispatch a log message. 
         * Add a config to either
         * <li> store logs (nonblock parsing)</li>
         * <li> display logs (nonblock parsing)</li>
         * <li> throw an error (block parsing)</li>
         * <li>add a safety mecanism to prevent infinite loop log when the same error is logged over and over in non-blocking mode</li>
         */
        private final function _log( message:String, isError:Boolean = false ):void
        {
            if( metadata.config.verbose ) { output( message ); }
            _logs.push( message );
            
            if( isError && enableErrorChecking )
            {
                LOG::P{ log.e( message ); }
                throw new EvalError( message );
            }
        }
        
        LOG::P
        private final function _traceGlobalPool():void
        {
            output( "---------------------" );
            output( "global pool:" );
            for( var member:String in _globalPool )
            {
                output( member + " = " + _globalPool[member] );
            }
            output( "---------------------" );
        }
        
        LOG::P
        private final function _traceLocalPool():void
        {
            output( "---------------------" );
            output( "local pool:" );
            for( var member:String in _localPool )
            {
                output( member + " = " + _localPool[member] );
            }
            output( "---------------------" );
        }
        
        /** @private */
        private final function _pathAsArray( path:String ):Array
        {
            LOG::P{ log.i( "_pathAsArray( \"" + path + "\" )" ); }
            var paths:Array;
            if( path.indexOf( "." ) > - 1 )
            {
                paths = path.split( "." );
            }
            else
            {
                paths = [ path ] ;
            }
            
            LOG::P{ log.d( "_pathAsArray = [" + path + "]" ); }
            return paths;
        }
        
        /** @private */
        private final function _createPath( path:String ):void
        {
            LOG::P{ log.i( "_createPath( \"" + path + "\" )" ); }
            var paths:Array = _pathAsArray( path );
            var prop:* = paths.shift();
            var subpath:String = "";
            var member:*;
            
            if( localscope[ prop ] == undefined )
            {
                if( isDigitNumber( prop ) )
                {
                    prop = parseInt( prop );
                }
                
                localscope[ prop ] = {};
                _localPool[ prop ] = localscope[ prop ];
            }
            
            if( paths.length > 0 )
            {
                subpath = prop;
                var scope:* = localscope[ prop ];
                
                for( var i:int = 0; i < paths.length ; i++ )
                {
                    member = paths[i];
                    subpath += "." + member;
                    
                    if( isDigitNumber( member ) )
                    {
                        member = parseInt( member );
                    }
                    
                    if( scope[member] == undefined )
                    {
                        scope[member] = {};
                        _localPool[ subpath ] = scope[member];
                    }
                    
                    scope = scope[member];
                }
            }
        }
        
        
        /* group: private checks */
        
        /** Indicates if the specified path does exist in the local scope. */
        private final function _doesExistInLocalScope( path:String ):Boolean
        {
            LOG::P{ log.i( "_doesExistInLocalScope( \"" + path + "\" )" ); }
            if( _localPool[ path ] != undefined )
            {
                return true;
            }
            
            var scope:* = localscope;
            var paths:Array = _pathAsArray( path );
            var subpath:*;
            var arrayIndex:Boolean;
            var len:int = paths.length ;
            for( var i:int ; i < len ; i++ )
            {
                arrayIndex = false;
                subpath    = paths[i];
                if( isDigitNumber( subpath ) )
                {
                    subpath = parseInt( subpath );
                    arrayIndex = true;
                }
                if( scope[ subpath ] == undefined )
                {
                    return false;
                }
                if( arrayIndex )
                {
                    _localPool[ paths.slice( 0, i ).join( "." ) + "." + subpath ] = scope[ subpath ];
                }
                scope = scope[ subpath ];
            }
            return true;
        }
        
        /** Indicates if the specified path does exist in the global scope. */
        private final function _doesExistInGlobalScope( path:String ):Boolean
        {
            LOG::P{ log.i( "_doesExistInGlobalScope( \"" + path + "\" )" ); }
            if( _globalPool[ path ] != undefined )
            {
                LOG::P{ _traceGlobalPool(); }
                return true;
            }
            
            //fix for global function in redtamarin
            if( path.indexOf( "." ) == -1 )
            {
                switch( path )
                {
                    case "decodeURI":
                    _globalPool[ path ] = decodeURI;
                    return true;
                    
                    case "decodeURIComponent":
                    _globalPool[ path ] = decodeURIComponent;
                    return true;
                    
                    case "encodeURI":
                    _globalPool[ path ] = encodeURI;
                    return true;
                    
                    case "encodeURIComponent":
                    _globalPool[ path ] = encodeURIComponent;
                    return true;
                    
                    case "isNaN":
                    _globalPool[ path ] = isNaN;
                    return true;
                    
                    case "isFinite":
                    _globalPool[ path ] = isFinite;
                    return true;
                    
                    case "parseInt":
                    _globalPool[ path ] = parseInt;
                    return true;
                    
                    case "parseFloat":
                    _globalPool[ path ] = parseFloat;
                    return true;
                    
                    case "escape":
                    _globalPool[ path ] = escape;
                    return true;
                    
                    case "unescape":
                    _globalPool[ path ] = unescape;
                    return true;
                    
                    case "isXMLName":
                    _globalPool[ path ] = isXMLName;
                    return true;
                }
            }
            
            var scope:*;
            var scopepath:String = "";
            var subpath:* = "";
            var paths:Array = _pathAsArray( path );
            var arrayIndex:Boolean;
            
            var foundScope:Boolean;
            var len:int = paths.length;
            for( var i:int = 0; i < len ; i++ )
            {
                if( !foundScope )
                {
                    if( i == 0 ) { scopepath = paths[i]; }
                    else { scopepath += "." + paths[i]; }
                    
                    if( _globalPool[ scopepath ] != undefined )
                    {
                        foundScope = true;
                        scope = _globalPool[ scopepath ];
                    }
                    else if( hasClassByName( scopepath ) )
                    {
                        foundScope = true;
                        scope = getDefinitionByName( scopepath );
                        LOG::P{ log.v( "GLOBAL POOL: " + scopepath ); }
                        _globalPool[ scopepath ] = scope;
                    }
                }
                else
                {
                    arrayIndex = false;
                    subpath = paths[i];
                    
                    if( isDigitNumber( subpath ) )
                    {
                        subpath = parseInt( subpath );
                        arrayIndex = true;
                    }
                    
                    if( scope[ subpath ] == undefined ) { return false; }
                    
                    if( arrayIndex )
                    {
                        LOG::P{ log.v( ">> GLOBAL POOL : " + scopepath + "." + paths[i - 1] + "." + subpath ); }
                        _globalPool[ scopepath + "." + paths[i - 1] + "." + subpath ] = scope[ subpath ];
                    }
                    else
                    {
                        LOG::P{ log.v( ">> GLOBAL POOL : " + scopepath + "." + subpath ); }
                        _globalPool[ scopepath + "." + subpath ] = scope[ subpath ];
                    }
                    
                    scope = scope[ subpath ];
                }
            }
            
            if( foundScope ) { return true; }
            
            return false;
        }
        
        /** Indicates if the specified path is valid. */
        private final function _isValidPath( path:String ):Boolean
        {
            LOG::P{ log.i( "_isValidPath( \"" + path + "\" )" ); }
            var paths:Array = _pathAsArray( path );
            var subpath:String;
            
            var len:int = paths.length;
            var reserved:Boolean;
            var future:Boolean;
            
            for( var i:int; i < len; i++ )
            {
                subpath  = paths[i];
                reserved = isReservedKeyword( subpath );
                future   = isFutureReservedKeyword( subpath );
                if( reserved || future )
                {
                    if( reserved ) { _log( format( metadata.strings.reservedKeyword, subpath ) ); }
                    if( future ) { _log( format( metadata.strings.futureReservedKeyword, subpath ) ); }
                    _log( format( metadata.strings.notValidPath, path ) );
                    return false;
                }
            }
            
//            if( metadata.config.security && !isAuthorized( path ) )
//            {
//                _log( format(path, metadata.strings.notAuthorizedPath) ) ;
//                return metadata.config.undefineable;
//            }
            
            return true;
        }
        
        
        /* group: private scanners */
        
        /**
         * Scan comments. 
         * - add a log/store mecanism for the comments
         *   so after a parsing we can review all the comments
         * - allow an option to ouput comment strings instead
         *   of the parsing result
         *   -> could be usefull to parse code for documentation
         */
        private final function _scanComments():void
        {
            LOG::P{ log.i( "_scanComments()" ); }
            next() ;
            switch( ch )
            {
                case "/":
                comments += "//";
                while( !isLineTerminator( ch ) && hasMoreChar() )
                {
                    next();
                    comments += ch;
                }
                comments += "\n";
                _scanSeparators();
                break;
                
                case "*":
                comments += "/*";
                var ch_:String = next();
                comments += ch_;
                
                while( (ch_ != "*") && (ch != "/") )
                {
                    ch_ = ch;
                    next();
                    comments += ch;
                    if( ch == "" ) { _log( metadata.strings.unterminatedComment, true ); break; }
                }
                comments += "\n";
                next();
                break;
                
                case "":
                default:
                _log( metadata.strings.errorComment, true );
            }
            
        }
        
        /** Scan separators. */
        private final function _scanSeparators():void
        {
            LOG::P{ log.i( "_scanSeparators()" ); }
            var scan:Boolean = true;
            while( scan )
            {
                switch( ch )
                {
                    /* note:
                    White Space
                    "\t" - \u0009 - TAB
                    "\v" - \u000B - VT
                    "\f" - \u000C - FF
                    " "  - \u0020 - SP
                    ???  - \u00A0 - NBSP
                    see: ECMA-262 spec 7.2 (PDF p23/188)
                     */
                    case "\u0009": 
                    case "\u000B": 
                    case "\u000C": 
                    case "\u0020": 
                    case "\u00A0":
                    next();
                    break;

                    /* note:
                    line terminators
                    "\n" - \u000A - LF
                    "\R" - \u000D - CR
                    ???  - \u2028 - LS
                    ???  - \u2029 - PS
                    see: ECMA-262 spec 7.3 (PDF p24/188)
                     */
                    case "\u000A": 
                    case "\u000D": 
                    case "\u2028": 
                    case "\u2029":
                    next();
                    break;

                    case "/":
                    _scanComments();
                    break;

                    default:
                    scan = false;
                }
            }
        }
        
        /**
         * Scan whitespaces.  
         * <p><b>White Space :</b></p>
         * <pre class="prettyprint">
         * "\t" - u0009 - TAB
         * "\v" - u000B - VT
         * "\f" - u000C - FF
         * " "  - u0020 - SP
         * ???  - u00A0 - NBSP
         * </pre>
         * <p><b>See :</b> ECMA-262 spec 7.2 (PDF p23/188)</p>
         */
        private final function _scanWhiteSpace():void
        {
            LOG::P{ log.i( "_scanWhiteSpace()" ); }
            var scan:Boolean = true;
            while( scan )
            {
                switch( ch )
                {
                    case "\u0009": 
                    case "\u000B": 
                    case "\u000C": 
                    case "\u0020": 
                    case "\u00A0":
                    next();
                    break;
                    
                    case "/":
                    _scanComments();
                    break;

                    default:
                    scan = false;
                }
            }
        }
        
        /** Scans identifiers. */
        private final function _scanIdentifier():String
        {
            LOG::P{ log.i( "_scanIdentifier()" ); }
            var id:String = "";
            if( isIdentifierStart( ch ) )
            {
                id += ch;
                next();
                while( isIdentifierPart( ch ) )
                {
                    id += ch;
                    next();
                }
            }
            else { _log( metadata.strings.errorIdentifier, true ); }
            
            LOG::P{ log.d( "_scanIdentifier() = " + id ); }
            return id;
        }
        
        /** Scan paths. */
        private final function _scanPath():String
        {
            LOG::P{ log.i( "_scanPath()" ); }
            var path:String = "";
            var subpath:String = "";
            if( isIdentifierStart( ch ) )
            {
                path += ch;
                next();
                while( isIdentifierPart( ch ) || (ch == ".") || (ch == "[") )
                {
                    if( ch == "[" )
                    {
                        next();
                        _scanWhiteSpace();
                        if( isDigit( ch ) )
                        {
                            subpath = String( _scanNumber() );
                            _scanWhiteSpace( );
                            path += "." + subpath;
                        }
                        else if( (ch == "\"") || (ch == "\'") )
                        {
                            subpath = _scanString( ch );
                            _scanWhiteSpace();
                            path += "." + subpath;
                        }
                        
                        if( ch == "]" ) { next(); continue; }
                    }
                    path += ch;
                    next();
                }
            }
            
            LOG::P{ log.d( "_scanPath = " + path ); }
            return path;
        }
        
        /** Scans the Strings. */
        private final function _scanString( quote:String ):String
        {
            LOG::P{ log.i( "_scanString( \"" + quote + "\" )" ); }
            var str:String = "";
            
            if( ch == quote )
            {
                while( (next() != "") )
                {
                    switch( ch )
                    {
                        case quote:
                        next();
                        LOG::P{ log.d( "_scanString = \"" + str + "\"" ); }
                        return str;
                        
                        case "\\":
                        /* note:
                           Escape Sequence
                           \ followed by one of ' " \ b f n r t v
                           or followed by x hexdigit hexdigit
                           or followed by u hexdigit hexdigit hexdigit hexdigit
                           see: ECMA-262 specs 7.8.4 (PDF p30 to p32/188)
                         */
                        switch( next() )
                        {
                            case "b": 
                            str += "\b"; //backspace \u0008
                            break;
                            
                            case "t": 
                            str += "\t"; //horizontal tab \u0009
                            break;
                            
                            case "n": 
                            str += "\n"; //line feed \u000A
                            break;
                            
                            case "v": 
                            str += "\v"; // vertical tab \u000B /* TODO: check \v bug */
                            break;
                            
                            case "f": 
                            str += "\f"; //form feed \u000C
                            break;
                            
                            case "r": 
                            str += "\r"; //carriage return \u000D
                            break;
                            
                            case "\"": 
                            str += "\""; //double quote \u0022
                            break;
                            
                            case "\'": 
                            str += "\'"; //single quote \u0027
                            break;
                            
                            case "\\": 
                            str += "\\"; //backslash \u005c
                            break;
                            
                            case "u":
                            //unicode escape sequence \uFFFF
                            var ucode:String = source.substring( pos, pos + 4 );
                            str += String.fromCharCode( parseInt( ucode, 16 ) );
                            pos += 4 ;
                            break;
                            
                            case "x":
                            //hexadecimal escape sequence \xFF
                            var xcode:String = source.substring( pos, pos + 2 );
                            str += String.fromCharCode( parseInt( xcode, 16 ) );
                            pos += 2;
                            break;
                            
                            default:
                            str += ch;
                        }
                        break;
                        
                        default:
                        if( !isLineTerminator( ch ) ) { str += ch; }
                        else { _log( metadata.strings.errorLineTerminator, true ); }
                    }
                }
            }
            
            _log( metadata.strings.errorString, true );
            LOG::P{ log.d( "_scanString = empty string" ); }
            return "";
        }
        
        /** Scans Numbers. */
        private final function _scanNumber():Number
        {
            LOG::P{ log.i( "_scanNumber()" ); }
            var value:Number;
            
            var num:String = "";
            // var oct:String  = "";
            var hex:String = "";
            var sign:String = "";
            var isSignedExp:String = "";
            
            if( ch == "-" ) 
            { 
                sign = "-"; 
                next(); 
            }
            
            if( ch == "0" )
            {
                next();
                if( (ch == "x") || (ch == "X") )
                {
                    next();
                    
                    while( isHexDigit( ch ) )
                    {
                        hex += ch;
                        next();
                    }
                    
                    if( hex == "" )
                    {
                        _log( metadata.strings.malformedHexadecimal, true );
                        LOG::P{ log.d( "_scanNumber = NaN" ); }
                        return NaN;
                    }
                    else
                    {
                        LOG::P{ log.d( "_scanNumber = 0x" + hex ); }
                        return Number( sign + "0x" + hex );
                    }
                }
                else
                {
                    num += "0";
                }
            }
            
            while( isDigit( ch ) )
            {
                num += ch;
                next();
            }
            
            if( ch == "." )
            {
                num += ".";
                next();
                
                while( isDigit( ch ) )
                {
                    num += ch;
                    next();
                }
            }
            
            if( ch == "e" || ch == "E" )
            {
                num += ch ;
                
                isSignedExp = next();
                
                if( (isSignedExp == "+") || (isSignedExp == "-") )
                {
                    num += isSignedExp;
                    next();
                }
                
                while( isDigit( ch ) )
                {
                    num += ch;
                    next();
                }
            }
            
            /*
            if( (num.charAt(0) == "0") && isOctalNumber( num ) )
            {
                value = parseInt( sign + num );
            }
            else
            {
                value = Number( sign + num );
            }
             */
            
            /* note:
               we do not support octal numbers anymore
             */
            value = Number( sign + num );
            
            if( !isFinite( value ) )
            {
                _log( metadata.strings.errorNumber, true );
                LOG::P{ log.d( "_scanNumber = NaN" ); }
                return NaN;
            }
            else
            {
                LOG::P{ log.d( "_scanNumber = " + value ); }
                return value;
            }
        }
        
        /** Scans objets litteral. */
        private final function _scanObject():Object
        {
            LOG::P{ log.i( "_scanObject()" ); }
            var obj:Object = {};
            var member:String;
            var value:*;
            
            if( ch == "{" )
            {
                next();
                _scanSeparators();
                if( ch == "}" ) { next(); return obj; }
                
                while( ch != "" )
                {
                    member = _scanIdentifier();
                    _scanWhiteSpace();
                    if( ch != ":" ) { break; }
                    next();
                    _inAssignement = true;
                    value = _scanValue();
                    _inAssignement = false;
                    if( !isReservedKeyword( member ) && !isFutureReservedKeyword( member ) ) { obj[member] = value; }
                    _scanSeparators();
                    if( ch == "}" ) { next(); return obj; }
                    else if( ch != "," ) { break; }
                    next();
                    _scanSeparators();
                }
            }
            
            _log( metadata.strings.errorObject, true );
            LOG::P{ log.d( "_scanObject = undefined" ); }
            return undefined;
        }
        
        /** Scan arrays litteral */
        private final function _scanArray():Array
        {
            LOG::P{ log.i( "_scanArray()" ); }
            var arr:Array = [];
            
            if( ch == "[" )
            {
                next();
                _scanSeparators();
                if( ch == "]" ) { next(); return arr; }
                
                while( ch != "" )
                {
                    arr.push( _scanValue() );
                    _scanSeparators();
                    if( ch == "]" ) { next(); return arr; }
                    else if( ch != "," ) { break; }
                    next();
                    _scanSeparators();
                }
            }
            
            _log( metadata.strings.errorArray, true );
            LOG::P{ log.d( "_scanArray = undefined" ); }
            return undefined;
        }
        
        /** Scans Functions. */
        private final function _scanFunction( fcnPath:String, pool:*, ref:* = null ):*
        {
            LOG::P{ log.i( "_scanFunction( \"" + fcnPath +  "\" )" ); }
            
            var args:Array = [];
            var fcnName:String;
            var fcnObj:*;
            var fcnObjScope:*;
            
            var isClass:Boolean = pool[ fcnPath ] is Class;
            
            if( fcnPath.indexOf( "." ) > - 1 ) { fcnName = fcnPath.split( "." ).pop(); }
            else { fcnName = fcnPath; }
            
            if( !isClass ) { fcnPath = fcnPath.split( "." + fcnName ).join( "" ); }
            
            _scanWhiteSpace();
            next();
            _scanSeparators();
            
            var foundEndParenthesis:Boolean = false;
            
            while( ch != "" )
            {
                if( ch == ")" ) { foundEndParenthesis = true; next(); break; }
                
                args.push( _scanValue() );
                _scanSeparators();
                
                if( ch == "," ) { next(); _scanSeparators(); }
                
                if( (pos == source.length) && (ch != ")") )
                {
                    _log( format( metadata.strings.unterminatedParenthesis, fcnPath ), true );
                    return metadata.config.undefineable;
                }
            }
            
            if( !foundEndParenthesis )
            {
                _log( format( metadata.strings.unterminatedParenthesis, fcnPath ), true );
                return metadata.config.undefineable;
            }
            
            if( isClass || (fcnPath == fcnName) )
            {
                fcnObj      = pool[ fcnPath ];
                fcnObjScope = null;
            }
            else
            {
                fcnObj      = pool[ fcnPath ][ fcnName ];
                fcnObjScope = pool[ fcnPath ];
            }
            
            if( !isClass && (ref == null) && (fcnObj == undefined) )
            {
                _log( format( metadata.strings.doesNotExist, fcnPath ), true );
                return metadata.config.undefineable;
            }
            else
            {
                
                if( _inConstructor > 0 )
                {
                    _inConstructor--;
                    try
                    {
                        return invoke( fcnObj as Class, args );
                    }
                    catch( e:Error )
                    {
                        _log( format( metadata.strings.malformedCtor, fcnPath, args, e.toString() ), true );
                        return metadata.config.undefineable;
                    }
                }
                
                if( metadata.config.security )
                {
                    var classname:String = getClassName( pool[ fcnPath ], true ); //get the full type as string
                        classname = classname.split( "::" ).join( "." ); //normalize the path
                    //var classref:Class   = getClassByName( classname );
                    //var methods:Array = getClassMethods( pool[ fcnPath ] );
                    
                    if( !isAuthorized( classname+".*" ) )
                    {
                        _log( format( metadata.strings.notAuthorizedFunction, fcnName, classname ) );
                        return metadata.config.undefineable;
                    }
                    
                    /* TODO:
                       we could add a "verifyMethod" option
                       to verify that the method exists first before calling it
                    */
                }
                
                var result:*;
                
                if( ref != null ) { result = ref[ fcnName ].apply( ref, args ); }
                else { result = fcnObj.apply( fcnObjScope, args ); }
                
                if( ch == "." )
                {
                    next();
                    return _scanFunction( _scanPath(), pool, result );
                }
                else
                {
                    if( !metadata.config.allowFunctionCall )
                    {
                        _log( format( metadata.strings.notFunctionCallAllowed, fcnName, args ), true );
                        return metadata.config.undefineable;
                    }
                    
                    return result;
                }
            }
            
            _log( metadata.strings.errorFunction );
        }
        
        /** Scans keywords. */
        private final function _scanKeyword( pre:String = "" ):*
        {
            LOG::P{ log.i( "_scanKeyword( \"" + pre +  "\" )" ); }
            var word:String = "";
            var baseword:String = _scanPath();
            
            word = pre + baseword;
            
            if( word == "" ) { return _ORC; }
            
            switch( word )
            {
                case "undefined":
                return metadata.config.undefineable;
                
                case "null": // Null literal
                return null;
                
                case "true": // Boolean literal
                return true;
                
                case "false":
                return false;
                
                // Number literals
                /* note:
                here we use shortcuts for common const to speedup the parsing
                but if they were not here they would be parsed just fine ;)
                 */
                case "NaN":
                return NaN;
                
                case "-Infinity":
                return -Infinity;
                
                case "Infinity":
                case "+Infinity":
                return Infinity;
                
                case "new":
                _inConstructor++;
                _scanWhiteSpace();
                baseword = _scanPath();
                
                default:
                var localRef:Boolean  = false;
                var globalRef:Boolean = false;
                var result:*;
                
                //if( metadata.config.allowAliases && aliases.containsAlias( baseword ) ) { baseword = aliases.getValue(baseword); }
                
                if( _doesExistInGlobalScope( baseword ) ) { globalRef = true; }
                else if( _doesExistInLocalScope( baseword ) ) { localRef = true; _singleValue = false; }
                else if( _isValidPath( baseword ) && !_inAssignement && !_inConstructor )
                {
                    _createPath( baseword );
                    localRef     = true;
                    _singleValue = false;
                }
                
                /* coded in the train listening RUN-DMC "It's Tricky"
                and refactored listening Ugly Kid joe "too bad" :D
                 */
                if( !_inAssignement && (source.indexOf( "=" ) > - 1) )
                {
                    if( localRef ) { _scanLocalAssignement( baseword ); }
                    else if( globalRef ) { _scanGlobalAssignement( baseword ); }
                }
                
                _scanSeparators(); // test the separators between the constructor and the (
                
                if( !localRef && !globalRef )
                {
                    _log( format( metadata.strings.notFoundInMemory, baseword ), true );
                    return metadata.config.undefineable;
                }
                
                if( localRef )
                {
                    if( ch == "(" ) { result = _scanFunction( baseword, _localPool ); }
                    else { result = _localPool[ baseword ]; }
                    return (pre == "-") ? -result : result;
                }
                
                if( globalRef )
                {
                    if( metadata.config.security && !isAuthorized( baseword ) )
                    {
                        _log( format( metadata.strings.notAuthorizedPath, baseword ) );
                        return metadata.config.undefineable;
                    }
                    
                    if( ch == "(" ) { result = _scanFunction( baseword, _globalPool ); }
                    else { result = _globalPool[ baseword ]; }
                    return (pre == "-") ? -result : result;
                }
                
                return metadata.config.undefineable;
            }
            
            _log( metadata.strings.errorKeyword, true );
        }
        
        /** Scans the global assignement of the specified path. */
        private final function _scanGlobalAssignement( path:String ):void
        {
            LOG::P{ log.i( "_scanGlobalAssignement( \"" + path +  "\" )" ); }
            if( metadata.config.security && !isAuthorized( path ) )
            {
                _log( format( metadata.strings.notAuthorizedPath, path ) );
                return;
            }
            
            var scope:*;
            var scopepath:String = "";
            var subpath:* = "";
            var paths:Array = _pathAsArray( path );
            var member:String = paths.pop();
            var foundScope:Boolean = false;
            var size:int = paths.length;
            
            for( var i:int ; i < size ; i++ )
            {
                if( !foundScope )
                {
                    if( i == 0 ) { scopepath = paths[i]; }
                    else { scopepath += "." + paths[i]; }
                    
                    if( hasClassByName( scopepath ) ) { foundScope = true; scope = getDefinitionByName( scopepath ); }
                }
                else
                {
                    subpath = paths[i];
                    if( isDigitNumber( subpath ) ) { subpath = parseInt( subpath ); }
                    if( scope[ subpath ] == undefined ) { return; }
                    scope = scope[ subpath ];
                }
            }
            
            _scanWhiteSpace();
            
            if( ch == "=" )
            {
                _singleValue = false;
                _inAssignement = true;
                next();
                _scanWhiteSpace();
                
                /* TODO: check if undefineable value is not preferable here */
                if( isLineTerminator( ch ) ) 
                { 
                    _log( metadata.strings.assignWithoutRHS, true ); 
                    return; 
                }
                
                var value:*         = _scanValue( );
                scope[ member ]     = value;
                _globalPool[ path ] = scope[member];
                _inAssignement      = false;
            }
        }
        
        /** Scans the root local assignement of the specified name value. */
        private final function _scanRootLocalAssignement( name:String ):void
        {
            LOG::P{ log.i( "_scanRootLocalAssignement( \"" + name +  "\" )" ); }
            _scanWhiteSpace();
            
            if( ch == "=" )
            {
                _singleValue = false;
                _inAssignement = true;
                next();
                _scanSeparators();
                
                /* TODO: check if undefineable value is not preferable here */
                if( isLineTerminator( ch ) ) { _log( metadata.strings.assignWithoutRHS, true ); return; }
                
                var value:* = _scanValue();
                
                if( value == _ORC ) { value = metadata.config.undefineable; }
                
                localscope[ name ] = value;
                _localPool[ name ] = localscope[ name ];
                LOG::P{ _traceLocalPool(); }
                _inAssignement = false;
            }
        }
        
        /** Scans the local assigment of the specified path. */
        private final function _scanLocalAssignement( path:String ):void
        {
            LOG::P{ log.i( "_scanLocalAssignement( \"" + path +  "\" )" ); }
            
            if( path.indexOf( "." ) == - 1 ) { _scanRootLocalAssignement( path ); return; }
            
            var paths:Array = _pathAsArray( path );
            var prop:*      = paths.shift();
            var member:*    = paths.pop();
            
            if( isDigitNumber( prop ) ) { prop = parseInt( prop ); }
            if( isDigitNumber( member ) ) { member = parseInt( member ); }
            
            var subpath:*;
            var scope:*;
            
            scope = localscope[ prop ];
            var size:int = paths.length;
            
            for( var i:int ; i < size ; i++ )
            {
                subpath = paths[i];
                if( isDigitNumber( subpath ) ) { subpath = parseInt( subpath ); }
                if( scope[ subpath ] == undefined ) { return; }
                scope = scope[ subpath ];
            }
            
            _scanWhiteSpace();
            if( ch == "=" )
            {
                _singleValue   = false;
                _inAssignement = true;
                next();
                _scanSeparators();
                
                /* TODO: check if undefineable value is not preferable here */
                if( isLineTerminator( ch ) ) { _log( metadata.strings.assignWithoutRHS, true ); return; }
                
                var value:*        = _scanValue();
                scope[ member ]    = value;
                _localPool[ path ] = scope[ member ];
                LOG::P{ _traceLocalPool(); }
                _inAssignement = false;
            }
        }
        
        /** Scans values. */
        private final function _scanValue():*
        {
            LOG::P{ log.i( "_scanValue()" ); }
            _scanSeparators();
            
            if( (pos == source.length) && !_1char )
            {
                LOG::P{ log.d( "prevent unecessary scan" ); }
                if( _inAssignement ) { _log( metadata.strings.RHSmissing, true ); }
                return;
            }
            
            switch( ch )
            {
                case "{" :
                return _scanObject();
                
                case "[" :
                return _scanArray();
                
                case "\"" : 
                case "\'" :
                return _scanString( ch );
                
                case "-" : 
                case "+" :
                if( isDigit( source.charAt( pos ) ) ) { return _scanNumber( ); }
                else  { var ch_:String = ch; next( ); return _scanKeyword( ch_ ); }
                
                case "0": case "1": case "2": case "3": case "4":
                case "5": case "6": case "7": case "8": case "9":
                return _scanNumber();
                
                default:
                return _scanKeyword();
            }
        }
        
        /* group: generic parser */
        
        /** Returns the String source representation of the parser (read-only). */
        public final function get source():String { return _source; }
        
        /** Returns the session logs for this parser. */
        public final function get logs():Array { return _logs; }
        
        /** Returns the current char in the parse process. */
        public final function getChar():String { return source.charAt( pos ); }
        
        /** Returns the char in the source to parse at the specified position. */
        public final function getCharAt( pos:uint ):String { return source.charAt( pos ); }
        
        /** Indicates if the source parser has more char. */
        public final function hasMoreChar():Boolean { return pos <= (source.length - 1); }
        
        /** Returns the next character in the source of this parser. */
        public final function next():String { ch = getChar( ); pos++; return ch; }
        
        /* group: methods */
        
        /** 
         * Evaluates the source and returns the serialized object.
         */
        public final function eval():*
        {
            LOG::P{ log.i( "eval()" ); }
            var value:* = _ORC;
            var tmp:*;
            
            if( source.length == 1 ) 
            { 
                _1char = true; 
            }
            
            while( hasMoreChar() )
            {
                _scanSeparators();
                
                if( !isAlpha( ch ) ) { next(); }
                
                tmp = _scanValue();
                
                _scanSeparators();
                
                if( tmp != _ORC ) { value = tmp; }
                
                /* note: poor man semicolon auto-insertion */
                if( ch == " " ) { ch = ";"; }
            }
            
            if( value == _ORC ) { value = undefined; }
            
            if( !_singleValue ) { value = localscope; }
            
            return value;
        }
        
        /**
         * Initialize the source of the serializer.
         */
        public final function load( source:String ):void
        {
            _source = source;
            pos = 0;
            ch  = "";
        }
    }
}
